#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include "mpi.h"

// the size of the array
static int ARRAY_SIZE = 8;

int max(int a, int b) {
	if(a > b) {
		return a;
	}	
	return b;
}

int min(int a, int b) {
	if(a < b) {
		return a;
	}	
	return b;
}

void debug(int array[][ARRAY_SIZE], int start, int end){
	int i, j;
	for(i=start;i<= end; i++) {
		for(j=0; j< ARRAY_SIZE; j++){
			printf("%d\t", array[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}


int countNeighbour(int array[][ARRAY_SIZE], int row, int column, int group_size) {
	int i,j, sum=0;
	//borderline cases are covered with help of the min and max functions
	for(i=max(row-1, 0); i<=min(row+1, group_size+1); i++) {
		for(j=max(column-1, 0); j<=min(column+1, ARRAY_SIZE-1); j++) {
			if(i == row && j == column) {
				continue;
			}
			sum += array[i][j];
		}
	}
	return sum;
}

int main(int argc, char *argv[])
{

	int rank, size, row, i, j, k, tag = 123, loop_size = 3; // the number of generation can be editted here

	MPI_Init(&argc, &argv);
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
	MPI_Comm_size(MPI_COMM_WORLD, &size);

	// calculate the group size
	// if array_size % size != 0, then the rest of the rows are distributed to the first array_size%size processes. 
	int group_size = (int)(ARRAY_SIZE/size);
	if(rank<ARRAY_SIZE%size) {
		group_size++;
	}
	int array[group_size+2][ARRAY_SIZE];
	
	// initialize the array
	srand(time(NULL));
	for(i=0; i< group_size+2; i++) {
		for(j=0; j< ARRAY_SIZE; j++){
			if(i == 0 || i == group_size+1) {
				//initialize first and last row of the matrix with 0 because nothing is copied there and we shouldn't use them for coungNeighbours
				array[i][j] = 0;
			} else {
				array[i][j] = rand()%2;
			}
		}
	}
	printf("Process %d: \n", rank);
	printf("Generation %d: \n", 0);
	debug(array, 1, group_size);
	
	for(k=0; k<loop_size; k++) {
	
		// send first row to previous process
		for(i = 1; i< size; i++){
			if(rank == i) {
				MPI_Send(array[1], ARRAY_SIZE, MPI_INT, i - 1 , tag, MPI_COMM_WORLD);
			}
			else if(rank == i - 1) {
				MPI_Recv(array[group_size + 1], ARRAY_SIZE, MPI_INT, i , tag, MPI_COMM_WORLD,
		             MPI_STATUS_IGNORE);
		    }
		}
		
		// send last row to next process
		for(i = 0; i< size-1; i++){
			if(rank == i) {
				MPI_Send(array[group_size], ARRAY_SIZE, MPI_INT, i + 1 , tag, MPI_COMM_WORLD);
			}
			else if(rank == i + 1) {
				MPI_Recv(array[0], ARRAY_SIZE, MPI_INT, i , tag, MPI_COMM_WORLD,
		             MPI_STATUS_IGNORE);
		    }
		}

		int result[group_size][ARRAY_SIZE], count;
		for(i=1; i<=group_size; i++){
			for(j=0; j<ARRAY_SIZE; j++){
				count = countNeighbour(array, i, j, group_size);
				//printf("count von i = %i und j = %i ist %i\n", i,j,count);
				if(array[i][j] == 1) {
					if(count < 2 || count > 3) {
						result[i-1][j] = 0;
					}
					if(count == 2 || count == 3) { 
						result[i-1][j] = 1;
					}
				} else {
					if(count == 3) {
						result[i-1][j] = 1;
					}else{
						result[i-1][j] = 0;
					}
				}
			}
		}
		printf("Process %d: \n", rank);
		printf("Generation %d: \n", k+1);
		
		// update the array with the result of calculation
		for(i=0; i<group_size; i++) {
			for(j=0; j<ARRAY_SIZE; j++) {
				array[i+1][j] = result[i][j];
			}
		}		
		
		debug(array, 1, group_size);
		//Use MPI_Waitall to make sure that all processes are finished before we do the next Generation
	}

    MPI_Finalize();
    return 0;
}



